package com.sudoku.solver;

import java.util.ArrayList;
import java.util.HashSet;

import com.sudoku.board.Board;
import com.sudoku.board.Tile;

public class XWing extends Strategy {

	private boolean changed;

	public XWing(Solver solver) {
		super(solver);
	}

	@Override
	public boolean solve(Board board) {
		stuck = true;
		boolean sameBox;
		// only 8 for i because we are doing 2 rows at a time
		for (int i = 0; i < 8; i++) {
			for (int j = i + 1; j < 9; j++) {
				// check if the rows are in the same box
				if (((i + 3) / 3) > j / 3) {
					sameBox = true;
				} else {
					sameBox = false;
				}
				if (xwing(board.getTilesInRow(i), board.getTilesInRow(j), sameBox, board)) {
					stuck = false;
				}
			}
		}
		return stuck;
	}

	/**
	 * Find the xwing pattern. Find 4 tiles that make a box with matching pencil number. If either of the two rows do NOT contain that number in any
	 * other box in int row but the 4, then delete the number from the any of the the tiles in the two columns (created by the 4), other than the
	 * original 4. Vice versa for columns.
	 * 
	 * @param firstTiles
	 *            , secondTiles
	 * @return if change to penciled numbers were made
	 */
	public boolean xwing(final ArrayList<Tile> firstTiles, final ArrayList<Tile> secondTiles, boolean sameBox, Board board) {
		changed = false;

		for (int i = 0; i < 9; i++) {

			if (firstTiles.get(i).isBlank() && secondTiles.get(i).isBlank()) {

				int j = 0;
				if (sameBox) {
					j = ((i + 3) / 3) * 3; // dont want to have both right tiles in same box as left tiles
				} else {
					j = i + 1;
				}

				for (; j < 9; j++) {
					
					if (firstTiles.get(j).isBlank() && secondTiles.get(j).isBlank()) {

						Tile upperLeftTile = firstTiles.get(i);
						Tile lowerLeftTile = secondTiles.get(i);
						Tile upperRightTile = firstTiles.get(j);
						Tile lowerRightTile = secondTiles.get(j);

						// find intersection of penciled numbers sets
						HashSet<Integer> intersection = new HashSet<Integer>(upperLeftTile.getPencilNumbers());
						intersection.retainAll(lowerLeftTile.getPencilNumbers());
						intersection.retainAll(upperRightTile.getPencilNumbers());
						intersection.retainAll(lowerRightTile.getPencilNumbers());

						for (Integer number : intersection) {

							boolean foundDuplicate = false;
							foundDuplicate = checkForDuplicate(number, firstTiles, secondTiles, upperLeftTile, lowerLeftTile, upperRightTile,
									lowerRightTile);

							// check columns for this number in other tiles
							if (foundDuplicate) {

								foundDuplicate = checkForDuplicate(number, board.getTilesInCol((int) ((upperLeftTile.getBox().getCoords().x
										* BOXES_PER_BOARD + upperLeftTile.getCoords().x))), board.getTilesInCol((int) ((upperRightTile.getBox()
										.getCoords().x * BOXES_PER_BOARD + upperRightTile.getCoords().x))), upperLeftTile, lowerLeftTile,
										upperRightTile, lowerRightTile);

								// found no duplicates in the cols, so we can erase this number from tiles in the rows (other than the 4 corners)
								if (!foundDuplicate) {
									if(removePencilNumber(number, firstTiles, secondTiles, upperLeftTile, lowerLeftTile, upperRightTile, lowerRightTile)){
										return changed;
									}
								}
							}
							// found no duplicates in the rows, so we can erase this number from tiles in the columns (other than the 4 corners)
							else {
								if(removePencilNumber(number,
										board.getTilesInCol((int) ((upperLeftTile.getBox().getCoords().x * BOXES_PER_BOARD + upperLeftTile
												.getCoords().x))), board.getTilesInCol((int) ((upperRightTile.getBox().getCoords().x
												* BOXES_PER_BOARD + upperRightTile.getCoords().x))), upperLeftTile, lowerLeftTile, upperRightTile,
										lowerRightTile)){
									return changed;
								}
							}
						}
					}
				}
			}
		}
		return changed;
	}

	private boolean removePencilNumber(Integer number, ArrayList<Tile> firstTiles, ArrayList<Tile> secondTiles, Tile upperLeftTile, Tile lowerLeftTile,
			Tile upperRightTile, Tile lowerRightTile) {
		for (Tile tile : firstTiles) {
			if (tile.isBlank() && !tile.equals(upperLeftTile) && !tile.equals(lowerLeftTile) && !tile.equals(upperRightTile)
					&& !tile.equals(lowerRightTile)) {
				if (tile.removePencilNumber(number)) {
					if(tile.getPencilNumbers().size()==0){
						System.err.println("help!");
					}
					System.out.println("XWing - removed " + number + " from tile " + tile.getCoords() + " from box " + tile.getBox().getCoords());
					changed = true;
				}
			}
		}

		for (Tile tile : secondTiles) {
			if (tile.isBlank() && !tile.equals(upperLeftTile) && !tile.equals(lowerLeftTile) && !tile.equals(upperRightTile)
					&& !tile.equals(lowerRightTile)) {
				if (tile.removePencilNumber(number)) {
					if(tile.getPencilNumbers().size()==0){
						System.err.println("help!");
					}
					System.out.println("XWing - removed " + number + " from tile " + tile.getCoords() + " from box " + tile.getBox().getCoords());
					changed = true;
				}
			}
		}
		return changed;
	}

	private boolean checkForDuplicate(Integer number, ArrayList<Tile> firstTiles, ArrayList<Tile> secondTiles, Tile upperLeftTile,
			Tile lowerLeftTile, Tile upperRightTile, Tile lowerRightTile) {
		boolean foundDuplicate = false;
		// check for this number in the first row
		for (Tile tile : firstTiles) {
			if (tile.isBlank() && !tile.equals(upperLeftTile) && !tile.equals(lowerLeftTile) && !tile.equals(upperRightTile)
					&& !tile.equals(lowerRightTile)) {
				if (tile.getPencilNumbers().contains(number)) {
					foundDuplicate = true;
					break;
				}
			}
		}
		// check for this number in the second row
		if (!foundDuplicate) {
			for (Tile tile : secondTiles) {
				if (tile.isBlank() && !tile.equals(upperLeftTile) && !tile.equals(lowerLeftTile) && !tile.equals(upperRightTile)
						&& !tile.equals(lowerRightTile)) {
					if (tile.getPencilNumbers().contains(number)) {
						foundDuplicate = true;
						break;
					}
				}
			}
		}
		return foundDuplicate;
	}
}
